// copyright 1993 Michael B. Johnson; some portions copyright 1994, MIT
// see COPYRIGHT for reuse legalities
//

// So here's the idea. Instead of the WWSampleList having a list of
// WWSamples, it now has a list of objects which conform to the
// <WWSample> protocol.  There are two objects that do so: WWSample (duh!)
// and WWSampleComponentList.  The first time we are asked to add a sample, 
// we look at the sample and and see if we already have a sample at that timestamp.  
// - If we don't, we merely insert the sample in the list.
// - else we do already have a sample, we look to see how many generators
//   this sample has.  
//   - If it only has one generator (i.e. generatorCount == 1)
//     then we see if the the generatorName is the same as the new sample.  
//     - if it is, we merely replace the old sample with the new one, freeing 
//       the old one (which may or may not free its data...).
//     - else the new sample at this timestamp has a 
//       different generator than the old one, *and* the old sample has only
//       one generator, we need to be a bit more fancy.  We malloc up a
//       WWSampleComponentList, move our old sample to it, then move our new
//       sample to it, and then replace our old sample in the WWSampleList with
//       our new WWSampleComponentList.
//   - else it has more than one generator, which means that we're looking
//     at a WWSampleComponentsList.  At that point, we do a set... on it,
//     and we're golden. 



#import "WWSampleList.h"

#import "WWSample.h"
#import "WWSampleComponentList.h"
#import "math.h"
#import "Protocol_WWRenderable.h"
#import "Protocol_WWSample.h"

@implementation WWSampleList

- init
{
  [super init];

  epsilon = .001;
  lerpSamples = YES;
  cacheLerps = YES;

  return self;

}

- initCount:(unsigned)numSlots
{
  [super initCount:numSlots];

  epsilon = .001;
  lerpSamples = YES;
  cacheLerps = YES;

  return self;

}

- awake
{
  [super awake];

  epsilon = .001;

  return self;

}

// we want to cover our freeObjects method here, because when building
// a list, we might actually have multiple samples that are using the
// same data.  This probably shouldn't happen, but the caching stuff
// might have it happen... Anyway, if it does happen, it will be with
// adjacent samples, so we only really need to hold on to the last sample
// data we free'ed.  If the one we're about to free has the same id as
// the one we just free'ed, mark that sample not to free its data and
// then free it.  If it isn't the one we just free
- freeObjects
{
  int            i, howMany = [self count];
  id             data, lastSampleData = nil;
  id <WWSample>  sample;


  //NXLogError("freeing objects for WWSampleList %p", self);
  for (i = 0; i < howMany; i++)
  {  sample = (id <WWSample>)[self objectAt:i];
     data = [sample data];
     if (lastSampleData == data) // yikes! it has the same data as what I just free'ed
     {  [sample setFreeData:NO];  // please don't free your data; it's already been free'ed
     }
     else
     {  lastSampleData = data;
     }
     [sample free];
  }
  return self;
}

- (double)epsilon { return epsilon; }

- setEpsilon:(double)newEpsilon { epsilon = newEpsilon; return self; }

- (BOOL)lerpSamples { return lerpSamples; }
- setLerpSamples:(BOOL)flag { lerpSamples = flag;  return self; }

- (BOOL)cacheLerps { return cacheLerps; }
- setSetCacheLerps:(BOOL)flag { cacheLerps = flag;  return self; }

// there really should be several different schemes for inserting samples.
// One way is that a new sample overrides an old one at the same timestamp,
// i.e. the old one is freed immediately.  Another way is that the 

- dumpList
{
   int            howMany = [self count],
                  i = howMany;
   id <WWSample>  aSample = nil;


   NXLogError("WWSampleList contents:\n");
   for (i = 0; i < howMany; i++)
   {  aSample = [self objectAt:i];
      NXLogError("(%d): %f %s with data %s\n", i, [aSample timestamp], [[aSample class] name], [[[aSample data] class] name]);
   }
   return self;
}

- addSample:(WWSample *)theSample
{
   int            howMany = [self count],
                  i = howMany;
   BOOL           found = NO;
   id <WWSample>  aSample = nil;
   double         delta;
   const char     *previousGen, *currentGen;


   //NXLogError("before adding new sample:\n");
   //[self dumpList];

   // this routine adds the sample to the list in the appropriate, ordered spot.
   // it also ensures that if there is a sample with the same timestamp as this
   // new one, it frees the old one and inserts this in the appropriate spot.
   // The naive insertion assumption is that samples get put in sequentially,
   // so we start from the back of the list and move towards the front...
   while (!found && i)
   {  i--;
      aSample = [self objectAt:i];
      delta = fabs((double)([aSample timestamp] - [theSample timestamp]));
      if (delta < epsilon)
      {  // okay, we're there.  Now we have to find out if we already have 
         // a sample with the same generator. 
         if ([aSample generatorCount] > 1)
         {  // it's already a list, just go ahead and set it with 
            // the contents of the new sample.  It's smart enough
            // to do the right thing.
            //NXLogError("adding new sample to WWSampleComponentList");
            [(WWSampleComponentList *)aSample addObject:theSample];
            found = YES;
         }
         else
         {  // okay, this guy is a single sample.  We have to see if it has
            // the same generator as the new sample.  If it does, just replace
            // the old one with the new one.  If, on the other hand, they
            // have different generators, we need to malloc up a WWSampleComponentList,
            // stick the old sample in there, and then stick the new one in,
            // then replace the old sample here with the newly alloced list
            previousGen = [aSample generatorName];
            currentGen = [theSample generatorName];
            if (!previousGen)
            {  if (!currentGen)
               {  // they're both NULL; replace old with new
                  //NXLogError("single sample: both generators NULL; replacing old with new");
                  [self replaceObject:aSample with:theSample];
                  [aSample free];
                  found = YES;
	       }
               else // previous was NULL, new one is !NULL, malloc up a list
	       {  // actually, make sure current isn't just a \0
                  if (!*currentGen)
		  {  // hah! one's NULL, but the other is ""
                     //NXLogError("single sample: one's NULL, and the other is \"\"; replacing old with new");
                     [self replaceObject:aSample with:theSample];
                     [aSample free];
                     found = YES;
                  }
	       }
	       {
	       }
            }
            else
            {  if (currentGen)  // they're both !NULL
               {  if (!strcmp(previousGen, currentGen))
		  {  // neither are NULL and they're the same
                     //NXLogError("single sample: both generators not NULL and they're the same; replacing old with new");
                     [self replaceObject:aSample with:theSample];
                     [aSample free];
                     found = YES;
                  }
                  else
                  {  // neither NULL, not the same, malloc up a list
                  }
	       }
               else // previous was !NULL, new one is NULL, malloc up a list
	       {  // actually, make sure previous isn't just a \0
                  if (!*previousGen)
		  {  // hah! one's NULL, but the other is ""
                     //NXLogError("single sample: one's NULL, and the other is \"\"; replacing old with new");
                     [self replaceObject:aSample with:theSample];
                     [aSample free];
                     found = YES;
                  }
	       }
            }
            if (!found)
            {  WWSampleComponentList  *newList = [[WWSampleComponentList alloc] initCount:2];

               //NXLogError("didn't find it; malloc'ing new WWSampleComponentList and adding the two, then replacing the old one with this list");
               [newList addObject:aSample];
               [newList addObject:theSample];
               [self replaceObject:aSample with:newList];
               found = YES;
            }
         }
      }
      else  // okay, it's not the same; is it later?
      {  if ([theSample timestamp] > [aSample timestamp])
         {  //NXLogError("punting and adding new sample at %d", i+1);
            [self insertObject:theSample at:(i + 1)];
            found = YES;
         }
      }     
   }
   if (!found)  // stick it in the front
   {  //NXLogError("sticking the sample in the front");
      [self insertObject:theSample at:0];
   }

   //NXLogError("after adding new sample:\n");
   //[self dumpList];

   return self;
}

// if we get a sample that is interpolated, and it's not the same as
// the one it's interpolated from, add it to the list if we've got the
// "cacheLerps" flag set

- (BOOL)isLerpable { return NO; }

- (id <WWSample>)sampleAtOrJustBefore:(float)theTimestamp
{
   id <WWSample>  aSample, theSample = nil;
   int            howMany = [self count],
                  i = howMany;
   BOOL           found = NO;
   double         delta;
   id <WWSample>  nextSample;
   float          u;
   WWSample       *newSample;


   // okay, the samples are ordered sequentially, so we'll step back
   // through time, trying to find a sample at the time we're looking at.
   // We first see if we've gone under the time, at which point we realize
   // that we need to stop and get the sample there.  If 

   while (!found && i)
   {  i--;
      aSample = [self objectAt:i];
      if ([aSample timestamp] < theTimestamp)
      {  delta = fabs((double)(theTimestamp - [aSample timestamp]));
	 //NXLogError("theTimestamp == %f\n", theTimestamp);
	 //NXLogError("[aSample timestamp] == %f\n", [aSample timestamp]);
	 //NXLogError("delta == %lf\n", delta);
         if (   !lerpSamples 
             || (i == (howMany - 1)) 
             || (delta < epsilon)) // we're not lerping or this is the last sample in the list or we're close enough
	 {  theSample = aSample;
            found = YES;
 	 }
         else // at this point, need to manufacture a new one by calling lerpWith:by: using this sample and the next
	 {  // or if this sample isn't linearly interpolatable, or if we're at the end of the list, we just hand back this sample.
            if ( (([[aSample data] respondsTo:@selector(isLerpable)]) && (![[aSample data] isLerpable])) || ((i+1) == howMany) )
	    {  theSample = aSample;
 	    }      
            else  // okay, looks like we've got to lerp it
	    {  //NXLogError("\ttheTimestamp == %f\n", theTimestamp);
	       //NXLogError("\t[aSample timestamp] == %f\n", [aSample timestamp]);
	       //NXLogError("\tdelta == %lf\n", delta);
               nextSample = [self objectAt:(i+1)];
               u = (theTimestamp - [aSample timestamp])/([nextSample timestamp] - [aSample timestamp]); 	    
               newSample = [[[WWSample alloc] init] setData:[[aSample data] lerpWith:[nextSample data] by:u]
                                                       timestamp:theTimestamp 
                                                       generator:"WWSampleList" weight:1.0];
               if (cacheLerps)
               {  [self addSample:newSample];
               }
               theSample = newSample;
            }
            found = YES;
 	 }
      }
      else  // need to check if it's close enough
      {  delta = fabs((double)([aSample timestamp] - theTimestamp));
         if (delta < epsilon)
	 {  theSample = aSample;
            found = YES;
 	 }
         else  // nope; keep trying...
	 {
         }
      }
   }
   return theSample;
}

// The sample list needs to be able to manufacture interpolated samples...


- (float)lastSampleIsAt {return [[self lastObject] timestamp]; }

- (unsigned long int)maxSampleBandwidth
{ 
   int   i, howMany = [self count];
   unsigned 
       long int sampleBandwidth, maxSampleBandwidth = 0; 


   for (i = 0; i < howMany; i++)
   {  sampleBandwidth = [[self objectAt:i] maxSampleBandwidth];
      if (sampleBandwidth > maxSampleBandwidth)
      {  maxSampleBandwidth = sampleBandwidth;
      }
   }
   return maxSampleBandwidth;
}

@end
